<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  <style>
    #cas {
      display: block;
      border: 1px solid;
      margin: auto;
    }
  </style>
  <title>3D旋转球</title>
</head>
<body style="background-color:#000">
<div>
  <canvas id='cas' width="1000" height="500">浏览器不支持canvas，请更新浏览器后再浏览</canvas>
  <div style="width:50px;margin:10px auto">
    <button id="controlBtn">停止</button>
  </div>
</div>
<script>
  var canvas = document.getElementById("cas"),
      ctx = canvas.getContext("2d"),
      vpx = canvas.width / 2,
      vpy = canvas.height / 2,
      Radius = 150,
      balls = [],
      angleX = Math.PI / 100,
      angleY = Math.PI / 100;

  window.addEventListener("mousemove", function(event) {
    var x = event.clientX - canvas.offsetLeft - vpx - document.body.scrollLeft - document.documentElement.scrollLeft;
    var y = event.clientY - canvas.offsetTop - vpy - document.body.scrollTop - document.documentElement.scrollTop;
    angleY = -x * 0.0001;
    angleX = -y * 0.0001;
  });

  var Animation = function() {
    this.init();
  };
  Animation.prototype = {
    isrunning: false,
    init: function() {
      balls = [];
      var num = 500;
      for (var i = 0; i <= num; i++) {
        var k = -1 + (2 * (i + 1) - 1) / num;
        var a = Math.acos(k);
        var b = a * Math.sqrt(num * Math.PI);
        var x = Radius * Math.sin(a) * Math.cos(b);
        var y = Radius * Math.sin(a) * Math.sin(b);
        var z = Radius * Math.cos(a);
        var b = new ball(x, y, z, 1.5);
        balls.push(b);
        b.paint();
      }
    },
    start: function() {
      this.isrunning = true;
      animate();
    },
    stop: function() {
      this.isrunning = false;
    }
  }

  function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    rotateX();
    rotateY();
    balls.sort(function(a, b) {
      return b.z - a.z;
    })
    for (var i = 0; i < balls.length; i++) {
      balls[i].paint();
    }
    if (animation.isrunning) {
      if ("requestAnimationFrame" in window) {
        requestAnimationFrame(animate);
      }
      else if ("webkitRequestAnimationFrame" in window) {
        webkitRequestAnimationFrame(animate);
      }
      else if ("msRequestAnimationFrame" in window) {
        msRequestAnimationFrame(animate);
      }
      else if ("mozRequestAnimationFrame" in window) {
        mozRequestAnimationFrame(animate);
      }
    }
  }

  function rotateX() {
    var cos = Math.cos(angleX);
    var sin = Math.sin(angleX);
    for (var i = 0; i < balls.length; i++) {
      var y1 = balls[i].y * cos - balls[i].z * sin;
      var z1 = balls[i].z * cos + balls[i].y * sin;
      balls[i].y = y1;
      balls[i].z = z1;
    }
  }

  function rotateY() {
    var cos = Math.cos(angleY);
    var sin = Math.sin(angleY);
    for (var i = 0; i < balls.length; i++) {
      var x1 = balls[i].x * cos - balls[i].z * sin;
      var z1 = balls[i].z * cos + balls[i].x * sin;
      balls[i].x = x1;
      balls[i].z = z1;
    }
  }

  var ball = function(x, y, z, r) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.r = r;
    this.width = 2 * r;
  }

  ball.prototype = {
    paint: function() {
      var fl = 450 //焦距
      ctx.save();
      ctx.beginPath();
      var scale = fl / (fl - this.z);
      var alpha = (this.z + Radius) / (2 * Radius);
      ctx.arc(vpx + this.x, vpy + this.y, this.r * scale, 0, 2 * Math.PI, true);
      ctx.fillStyle = "rgba(255,255,255," + (alpha + 0.5) + ")"
      ctx.fill();
      ctx.restore();
    }
  }

  var animation = new Animation();
  animation.start();

  document.getElementById("controlBtn").onclick = function() {
    this.innerText === "开始" ? this.innerText = "停止" : this.innerText = "开始";
    this.innerText === "开始" ? animation.stop() : animation.start();
    ;
  }
</script>
</body>
</html>